package main

import (
	"fmt"
)

/*
问题 1：Go 语言是用嵌入字段实现了继承吗？
	Go 语言中根本没有继承的概念，它所做的是通过嵌入字段的方式实现了类型之间的组合。

问题 2：值方法和指针方法都是什么意思，有什么区别？
	方法的接收者类型必须是某个自定义的数据类型，而且不能是接口类型或接口的指针类型。
	所谓的值方法，就是接收者类型是非指针的自定义数据类型的方法。

	1.值方法的接收者是该方法所属的那个类型值的一个副本。
	在该方法内对该副本的修改一般都不会体现在原值上，除非这个类型本身是某个引用类型（比如切片或字典）的别名类型。
	而指针方法的接收者，是该方法所属的那个基本类型值的指针值的一个副本。
	在这样的方法内对该副本指向的值进行修改，却一定会体现在原值上。

	2.一个自定义数据类型的方法集合中仅会包含它的所有值方法，而该类型的指针类型的方法集合却囊括了前者的所有方法，包括所有值方法和所有指针方法。

	3.


 */

/*
	结构体类型AnimalCategory代表了动物的基本分类法，其中有 7 个string类型的字段，分别表示各个等级的分类。
 */
type  AnimalCategory struct {
	kingdom string // 界。
	phylum string // 门。
	class  string // 纲。
	order  string // 目。
	family string // 科。
	genus  string // 属。
	species string // 种。
}

/*
	声明了一个结构体类型，名叫Animal。它有两个字段。一个是string类型的字段scientificName，代表了动物的学名。
	而另一个字段声明中只有AnimalCategory，它正是我在前面编写的那个结构体类型的名字。

	Animal类型中的字段声明AnimalCategory代表了什么？
		字段声明AnimalCategory代表了Animal类型的一个嵌入字段。
		Go语言规范规定，如果一个字段的声明中只有字段的类型名而没有字段的名称，那么它就是一个嵌入字段，也可以被称为匿名字段。
		可以通过此类型变量的名称后跟“.”，再后跟嵌入字段类型的方式引用到该字段。也就是说，嵌入字段的类型既是类型也是名称。
 */
type Animal struct {
	// 学名
	scientificName string
	// 动物基本分类
	AnimalCategory
}

/*
	接收者声明可以看出它隶属于AnimalCategory类型
	通过该方法的接收者名称ac，可以在其中引用到当前值的任何一个字段，或者调用到当前值的任何一个方法（也包括String方法自己）
 */
func (ac AnimalCategory) String() string {
	return fmt.Sprintf("%s%s%s%s%s%s%s",
		ac.kingdom, ac.phylum, ac.class, ac.order,
		ac.family, ac.genus, ac.species)
}

/*
	Category方法的接收者类型是Animal，接收者名称是a。
	在该方法中，通过表达式a.AnimalCategory选择到了a的这个嵌入字段，然后又选择了该字段的String方法并调用了它。
 */
func (a Animal) Category() string  {
	return a.AnimalCategory.String()
}

func (a Animal) String() string  {
	return fmt.Sprintf("%s (category: %s)",
		a.scientificName, a.AnimalCategory)
}


/*
	结构体类型Cat中有一个嵌入字段Animal，而Animal类型还有一个嵌入字段AnimalCategory。
	在这种情况下，“屏蔽”现象会以嵌入的层级为依据，嵌入层级越深的字段或方法越可能被“屏蔽”。
 */
type Cat struct {
	name string
	Animal
}

/*
	当调用Cat类型值的String方法时，如果该类型确有String方法，那么嵌入字段Animal和AnimalCategory的String方法都会被“屏蔽”。

	如果该类型没有String方法，那么嵌入字段Animal的String方法会被调用，而它的嵌入字段AnimalCategory的String方法仍然会被屏蔽。

	只有当Cat类型和Animal类型都没有String方法的时候，AnimalCategory的String方法菜会被调用。
 */
func (cat Cat) String() string {
	return fmt.Sprintf("%s (category: %s, name: %q)",
		cat.scientificName, cat.Animal.AnimalCategory, cat.name)
}

/*
	方法SetName的接收者类型是*Cat。Cat左边再加个*代表的就是Cat类型的指针类型。

*/
func (cat *Cat) SetName(name string)  {
	cat.name = name
}

func main() {

	// 字面量初始化了一个AnimalCategory类型的值，并把它赋给了变量category
	category := AnimalCategory{species: "cat"}
	fmt.Printf("The animal category: %s\n", category)

	/*
		声明了一个Animal类型的变量animal并对它进行初始化。
		把字符串值"American Shorthair"赋给它的字段scientificName，并把前面声明过的变量category赋给它的嵌入字段AnimalCategory。
	 */
	animal :=Animal{
		scientificName: "American Shorthair",
		AnimalCategory: category,
	}
	/*
		使用fmt.Printf函数和%s占位符试图打印animal的字符串表示形式，相当于调用animal的String方法。
		虽然没有为Animal类型编写String方法，但这样做是没问题的。因为在这里，嵌入字段AnimalCategory的String方法会被当做animal的方法调用。

		那如果为Animal类型编写一个String方法呢？这里会调用哪一个呢？
			animal的String方法会被调用。这时，嵌入字段AnimalCategory的String方法被“屏蔽”了。
			注意，只要名称相同，无论这两个方法的签名是否一致，被嵌入类型的方法都会“屏蔽”掉嵌入字段的同名方法。
	 */
	fmt.Printf("The animal: %s\n", animal)


}
